// GRUPO 02 - TURMA A
// Gabriel Tessaroli	 – 4321350
// Lucas Nunes Arvani	 – 7986802
// Lucas Fúrio Franco  – 6793010

#include "mpi.h"
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Header
void read_ppm(int param);
void imsmooth(int param, int rank);
int p(int row,int col, int pixel, int cols);
void print_time(struct timeval begin, struct timeval end, int flag);

int main(int argc, char *argv[]) {

	int rank;
	int param = 5;
	struct timeval begin_program, end_program;

	// marcador de tempo.
	gettimeofday(&begin_program, NULL);

	// Inicialização do MPI
	MPI_Init(&argc, &argv);

	MPI_Comm_rank(MPI_COMM_WORLD, &rank);

	// verefica se é o primeiro processo
	// ou algum outro qualquer.
	if (rank == 0) {
		read_ppm(param);
	} else {
		imsmooth(param,rank);
	}

	MPI_Finalize();

	// marcador de tempo
	gettimeofday(&end_program, NULL);
	print_time(begin_program, end_program, 1);

	return 0;
}

// Processo a ser chamado pelo primeiro processo
void imsmooth(int param, int rank) {

	MPI_Status stat;
	int cols, rows, border = param /2;
	int tag = 1;

	// Recebe as informações necessárias para inicializar o processo
	MPI_Recv(&cols, 1, MPI_INT, 0, tag, MPI_COMM_WORLD, &stat);
	MPI_Recv(&rows, 1, MPI_INT, 0, tag, MPI_COMM_WORLD, &stat);

	int size = cols*rows*3;
	// reserva espaço na memória para o calculo do algoritmo
	unsigned char *smooth = (unsigned char*) calloc(sizeof(unsigned char),size);
	size = cols*(rows+2*border)*3;
	unsigned char *image = (unsigned char*) malloc(sizeof(unsigned char)*size);

	// recebe mensagem com a imagem.
	MPI_Recv(image, size, MPI_UNSIGNED_CHAR, 0, tag, MPI_COMM_WORLD, &stat);


	// realiza o calculo de smooth para o trecho recebido
	int i, j;
	int a, b, c;
	int avg[3], sum[3];
	for(i = border; i < (rows+border); ++i) {
		for(j = border; j < (cols-border); ++j) {
			sum[0] = sum[1] = sum[2] = 0;

			for(a = (i-border); a <= (i+border); ++a)
				for(b = (j-border); b <= (j+border); ++b)
					for(c = 0; c < 3; ++c)
						sum[c] += image[p(a,b,c,cols)];

			for(c = 0; c < 3; ++c)
				smooth[p(i-border,j-border,c,cols)] = sum[c] / (param*param);
		}
	}

	// envia resultado de volta para o processo principal
	size = cols*rows*3;
	MPI_Send(smooth, size, MPI_UNSIGNED_CHAR, 0, tag, MPI_COMM_WORLD);

	// libera memória.
	free (image);
	free (smooth);
}

int p(int row, int col, int pixel, int cols) {
	return ((row*cols) + col)*3 + pixel;
}

// Primeiro processo a ser feito.
void read_ppm(int param) {
	struct timeval begin_exec, end_exec;
	int nro_proc; //numero de processos a ser executado

	char key[3];
	scanf("%s", key);

	// tamanho da matriz
	int border = param / 2;


	int r_cols, r_rows, max;
	scanf("%d %d %d",&r_cols, &r_rows, &max);

	int cols = r_cols + (2*border), rows = r_rows + (2*border);
	unsigned char *image = (unsigned char *) malloc(cols*rows*3 * sizeof(unsigned char));

	int i, j;
	int r, g, b;
	for(i = border; i < (rows-border); ++i) {
		for(j = border; j < (cols-border); ++j) {
			scanf("%d %d %d", &r, &g, &b);
			image[p(i,j,0,cols)] = (unsigned char)r;
			image[p(i,j,1,cols)] = (unsigned char)g;
			image[p(i,j,2,cols)] = (unsigned char)b;
		}
	}

	gettimeofday(&begin_exec, NULL);

	// Inicializa a criação de processos
	MPI_Comm_size(MPI_COMM_WORLD, &nro_proc);

	int tag = 1;
	int rows_per_proc = r_rows / nro_proc;
	int pos, size = ((rows_per_proc + 2*border) * cols)*3;

	// cria processo para cada trecho da imagem
	for(i = 0; i < (nro_proc-1); ++i) {
		MPI_Send(&cols, 1, MPI_INT, i+1, tag, MPI_COMM_WORLD);
		MPI_Send(&rows_per_proc, 1, MPI_INT, i+1, tag, MPI_COMM_WORLD);

		pos = p((i*rows_per_proc),0,0,cols);
		MPI_Send(image+(pos), size, MPI_UNSIGNED_CHAR, i+1, tag, MPI_COMM_WORLD);
	}

	int rows_smooth = (r_rows / nro_proc) + (r_rows % nro_proc);
	unsigned char *smooth = (unsigned char *) malloc(3*cols*rows_smooth*sizeof(unsigned char));
	int sum[3];
	int a,c;

	// realiza o algoritmo para a ultimo bloco da imagem
	for(i = border; i < (rows_smooth+border); ++i) {
		for(j = border; j < (cols-border); ++j) {
			sum[0] = sum[1] = sum[2] = 0;

			for(a = (i-border); a <= (i+border); ++a)
				for(b = (j-border); b <= (j+border); ++b)
					for(c = 0; c < 3; ++c)
						sum[c] += image[p(a+((nro_proc-1)*rows_per_proc),b,c,cols)];

			for(c = 0; c < 3; ++c)
				smooth[p(i-border,j-border,c,cols)] = sum[c] / (param*param);
		}
	}

	// espera todas os processos finalizarem
	size = (rows_per_proc * cols)*3;
	MPI_Status stat;

	// recebe resultados dos processos
	for(i = 0; i < (nro_proc-1); ++i) {
		pos = p((i*rows_per_proc)+border,0,0,cols);
		MPI_Recv(image+(pos), size, MPI_UNSIGNED_CHAR, i+1, tag, MPI_COMM_WORLD, &stat);
	}

	// refaz imagem final
	for(i = (nro_proc-1)*rows_per_proc; i < (rows-border); ++i)
		for(j = border; j < (cols-border); ++j)
			for(c = 0; c < 3; ++c)
				image[p(i,j,c,cols)] = smooth[p(i-((nro_proc-1)*rows_per_proc), j, c, cols)];

	// marcador de tempo
	gettimeofday(&end_exec, NULL);

	print_time(begin_exec, end_exec, 0);

	// grava resultado final em um arquivo
	FILE *out = fopen("out.ppm", "w");

	fprintf(out, "P3\n");
	fprintf(out, "%d %d\n%d", r_cols, r_rows, max);

	for(i = border; i < (rows-border); ++i) {
		for(j = border; j < (cols-border); ++j) {
			if (!((j-border) % 6)) fprintf(out, "\n");
			fprintf(out, "%d %d %d ", image[p(i,j,0,cols)], image[p(i,j,1,cols)], image[p(i,j,2,cols)]);
		}
	}

	free(smooth);
	free(image);
}

// imprime tempo de execução.
void print_time(struct timeval begin, struct timeval end, int flag) {
	if (flag) {
		printf("Program Time: ");
	} else {
		printf("Execution time: ");
	}

	if (end.tv_usec < begin.tv_usec) {
		printf("%ld.%06lds.\n", end.tv_sec - begin.tv_sec - 1, (-1)*(end.tv_usec - begin.tv_usec));
	} else {
		printf("%ld.%06lds.\n", end.tv_sec - begin.tv_sec, (end.tv_usec - begin.tv_usec));
	}
}
